package com.darren.framework.dao.impl;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.util.TypeUtils;
import com.darren.framework.bean.PageBean;
import com.darren.framework.dao.GenericDao;
import com.darren.framework.entity.BaseEntity;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate5.HibernateTemplate;
import org.springframework.stereotype.Repository;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Repository(value = "genericDao")
public abstract class GenericDaoImpl<T extends BaseEntity, PK extends Serializable> extends BaseDaoImpl implements GenericDao<T, PK> {

    protected Logger logger = LogManager.getLogger(getClass());

    @Autowired
    protected HibernateTemplate template;

    @Autowired
    protected SessionFactory sessionFactory;

    private Class<T> clazz;

    protected Map<String, Field> fieldMap;

    @SuppressWarnings("unchecked")
    public GenericDaoImpl() {
        clazz = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        fieldMap = getNameAndType(clazz);
    }

    private Map<String, Field> getNameAndType(Class<?> clazz) {
        Map<String, Field> fieldMap = new HashMap<String, Field>();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            Annotation[] annotations = field.getDeclaredAnnotations();
            for (Annotation annotation : annotations) {
                if ("javax.persistence.Column".equals(annotation.annotationType().getName())) {
                    Class<?> clz = field.getClass();// .getGenericType().getClass();
                    Map<String, Field> childFiels = getNameAndType(clz);
                    for (String key : childFiels.keySet()) {
                        fieldMap.put(field.getName() + "." + key, childFiels.get(key));
                    }
                    if (childFiels.size() == 0) {
                        fieldMap.put(field.getName(), field);
                    }
                    break;
                } else if ("javax.persistence.Transient".equals(annotation.annotationType().getName())) {
                    break;
                }
            }
        }
        return fieldMap;
    }

    @Override
    public Map<String, Object> constructHQLQuery(JSONObject queryJson) {
        Map<String, Object> queryMap = new HashMap<String, Object>();
        List<Object> parameters = new ArrayList<Object>();
        String likes = "";
        String others = "";
        for (Map.Entry<String, Object> queryEntry : queryJson.entrySet()) {
            String queryKey = queryEntry.getKey();
            if ("likes".equals(queryKey)) {
                JSONObject likesJson = queryJson.getJSONObject(queryKey);
                for (Map.Entry<String, Object> likesEntry : likesJson.entrySet()) {
                    String likesKey = likesEntry.getKey();
                    String value = likesJson.getString(likesKey);
                    if (value != null && !"".equals(value.trim())) {
                        if (fieldMap.containsKey(likesKey)) {
                            likes += " or " + likesKey + " like ? ";
                            parameters.add("%" + value + "%");
                        }
                    }
                }
                if (!"".equals(likes)) {
                    likes = "(" + likes.substring(3) + ")";
                }
            } else {
                String operator = "";
                if ("like".equals(queryKey)) {
                    operator = " like ";
                } else if ("equals".equals(queryKey)) {
                    operator = " = ";
                } else if ("greaterThan".equals(queryKey)) {
                    operator = " > ";
                } else if ("greaterThanOrEquals".equals(queryKey)) {
                    operator = " >= ";
                } else if ("lessThan".equals(queryKey)) {
                    operator = " < ";
                } else if ("lessThanOrEquals".equals(queryKey)) {
                    operator = " <= ";
                }
                if (!"".equals(operator)) {
                    JSONObject otherJson = queryJson.getJSONObject(queryKey);
                    for (Map.Entry<String, Object> otherEntry : otherJson.entrySet()) {
                        String otherKey = otherEntry.getKey();
                        String value = otherJson.getString(otherKey);
                        if (value != null && !"".equals(value.trim())) {
                            if (fieldMap.containsKey(otherKey)) {
                                others = " and " + otherKey + operator + " ? ";
                                Field field = fieldMap.get(otherKey);
                                if ("like".equals(operator)) {
                                    parameters.add("%" + value + "%");
                                } else {
                                    parameters.add(TypeUtils.castToJavaBean(value, field.getType()));
                                }
                            }
                        }
                    }
                }
            }
        }
        queryMap.put("hql", others + " and " + likes);
        queryMap.put("parameters", parameters);
        return queryMap;
    }

    @Override
    public T save(T entity) {
        Session session = sessionFactory.getCurrentSession();
        session.save(entity);
        session.flush();
        return entity;
    }

    @Override
    public T merge(T entity) {
        Session session = sessionFactory.getCurrentSession();
        entity = (T) session.merge(entity);
        session.flush();
        return entity;
    }

    @Override
    public T findById(PK id) {
        Session session = sessionFactory.getCurrentSession();
        return (T) session.get(clazz, id);
    }


    @Override
    public T update(T entity) {
        Session session = sessionFactory.getCurrentSession();
        if (entity != null) {
            Object object = session.merge(entity);
            return (T) object;
        }
//        session.update(entity);
//        session.flush();
        return entity;
    }

    @Override
    public void delete(T entity) {
        Session session = sessionFactory.getCurrentSession();
        session.delete(entity);
    }

    @Override
    public void deleteById(PK id) {
        T entity = findById(id);
        if (entity != null) {
            delete(entity);
        }
    }

    @Override
    public List<T> getAll() {
        Session session = sessionFactory.getCurrentSession();
        Query<T> query = session.createQuery(" from " + clazz.getSimpleName(), clazz);
        return query.list();
    }

    @Override
    public PageBean queryForPageBySQL(String sql, Integer pageSize, Integer currentPage, Object... objs) {
        Session session = sessionFactory.getCurrentSession();
        //Query query = session.createSQLQuery(sql).addEntity(clazz);
        NativeQuery<T> query = session.createNativeQuery(sql, clazz);
        for (int i = 0; i < objs.length; i++) {
            query.setParameter(i, objs[i]);
        }

        //得到滚动结果集
        ScrollableResults scroll = query.scroll();

        //滚动到最后一行
        scroll.last();
        int count = scroll.getRowNumber() + 1;

        //设置分页位置
        if (count >= currentPage * pageSize) {
            query.setFirstResult((currentPage - 1) * pageSize);
        } else {
            currentPage = count / pageSize + 1;
            query.setFirstResult(count - count % pageSize);
        }
        query.setMaxResults(pageSize);
        PageBean pageBean = new PageBean();
        pageBean.setPageSize(pageSize);
        pageBean.setCurrentPage(currentPage);
        pageBean.setTotalRow(count);
        pageBean.setList(query.list());
        pageBean.init();
        return pageBean;
    }

    @Override
    public List<T> queryByCondition(String hql, Object... objs) {
        Session session = sessionFactory.getCurrentSession();
//		Query query = session.createQuery(hql);
        Query<T> query = session.createQuery(hql, clazz);
        for (int i = 0; i < objs.length; i++) {
            query.setParameter(i, objs[i]);
        }
        return query.list();
    }

    @Override
    public PageBean queryForPageByHQL(String hql, Integer pageSize, Integer currentPage, Object... objs) {
        Session session = sessionFactory.getCurrentSession();
        Query<T> query = session.createQuery(hql, clazz);
        for (int i = 0; i < objs.length; i++) {
            query.setParameter(i, objs[i]);
        }

        //得到滚动结果集
        ScrollableResults scroll = query.scroll();

        //滚动到最后一行
        scroll.last();
        int count = scroll.getRowNumber() + 1;

        //设置分页位置
        if (count >= currentPage * pageSize) {
            query.setFirstResult((currentPage - 1) * pageSize);
        } else {
            currentPage = count / pageSize + 1;
            query.setFirstResult(count - count % pageSize);
        }
        query.setMaxResults(pageSize);
        PageBean pageBean = new PageBean();
        pageBean.setPageSize(pageSize);
        pageBean.setCurrentPage(currentPage);
        pageBean.setTotalRow(count);
        pageBean.setList(query.list());
        pageBean.init();
        return pageBean;
    }
}